/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package math;

import com.google.common.base.Preconditions;
import org.apache.mahout.math.*;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;

/** Implements vector as an array of doubles */
public class MyDenseVector extends AbstractVector implements Serializable{
    public double[] getValues() {
        return values;
    }

    public void setValues(double[] values) {
        this.values = values;
    }

    private double[] values;

    /** For serialization purposes only */
    public MyDenseVector() {
        super(0);
    }

    /** Construct a new instance using provided values */
    public MyDenseVector(double[] values) {
        this(values, false);
    }

    public MyDenseVector(double[] values, boolean shallowCopy) {
        super(values.length);
        this.values = shallowCopy ? values : values.clone();
    }

    public MyDenseVector(MyDenseVector values, boolean shallowCopy) {
        this(values.values, shallowCopy);
    }

    /** Construct a new instance of the given cardinality */
    public MyDenseVector(int cardinality) {
        super(cardinality);
        this.values = new double[cardinality];
    }

    /**
     * Copy-constructor (for use in turning a sparse vector into a dense one, for example)
     * @param vector The vector to copy
     */
    public MyDenseVector(Vector vector) {
        super(vector.size());
        values = new double[vector.size()];
        for (Element e : vector.nonZeroes()) {
            values[e.index()] = e.get();
        }
    }

    @Override
    public double dot(Vector x) {
        if (!x.isDense()) {
            return super.dot(x);
        } else {

            int size = x.size();
            if (values.length != size) {
                throw new CardinalityException(values.length, size);
            }

            double sum = 0;
            for (int n = 0; n < size; n++) {
                sum += values[n] * x.getQuick(n);
            }
            return sum;
        }
    }

    @Override
    protected Matrix matrixLike(int rows, int columns) {
        return new DenseMatrix(rows, columns);
    }

    @SuppressWarnings("CloneDoesntCallSuperClone")
    @Override
    public MyDenseVector clone() {
        return new MyDenseVector(values.clone());
    }

    /**
     * @return true
     */
    @Override
    public boolean isDense() {
        return true;
    }

    /**
     * @return true
     */
    @Override
    public boolean isSequentialAccess() {
        return true;
    }

    @Override
    protected double dotSelf() {
        double result = 0.0;
        int max = size();
        for (int i = 0; i < max; i++) {
            result += values[i] * values[i];
        }
        return result;
    }

    @Override
    public double getQuick(int index) {
        return values[index];
    }

    @Override
    public MyDenseVector like() {
        return new MyDenseVector(size());
    }

    @Override
    public void setQuick(int index, double value) {
        invalidateCachedLength();
        values[index] = value;
    }

    @Override
    public void incrementQuick(int index, double increment) {
        invalidateCachedLength();
        values[index] += increment;
    }

    @Override
    public Vector assign(double value) {
        invalidateCachedLength();
        Arrays.fill(values, value);
        return this;
    }

    @Override
    public int getNumNondefaultElements() {
        return values.length;
    }

    public Vector assign(MyDenseVector vector) {
        // make sure the data field has the correct length
        if (vector.values.length != this.values.length) {
            this.values = new double[vector.values.length];
        }
        // now copy the values
        System.arraycopy(vector.values, 0, this.values, 0, this.values.length);
        return this;
    }

    @Override
    public void mergeUpdates(OrderedIntDoubleMapping updates) {
        int numUpdates = updates.getNumMappings();
        int[] indices = updates.getIndices();
        double[] values = updates.getValues();
        for (int i = 0; i < numUpdates; ++i) {
            this.values[indices[i]] = values[i];
        }
    }

    @Override
    public Vector viewPart(int offset, int length) {
        if (offset < 0) {
            throw new IndexException(offset, size());
        }
        if (offset + length > size()) {
            throw new IndexException(offset + length, size());
        }
        return new VectorView(this, offset, length);
    }

    @Override
    public double getLookupCost() {
        return 1;
    }

    @Override
    public double getIteratorAdvanceCost() {
        return 1;
    }

    @Override
    public boolean isAddConstantTime() {
        return true;
    }

    /**
     * Returns an iterator that traverses this Vector from 0 to cardinality-1, in that order.
     */
    @Override
    public Iterator<Element> iterateNonZero() {
        return new NonDefaultIterator();
    }

    @Override
    public Iterator<Element> iterator() {
        return new AllIterator();
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof DenseVector) {
            // Speedup for DenseVectors
            return Arrays.equals(values, ((MyDenseVector) o).values);
        }
        return super.equals(o);
    }

    public void addAll(Vector v) {
        if (size() != v.size()) {
            throw new CardinalityException(size(), v.size());
        }

        for (Element element : v.nonZeroes()) {
            values[element.index()] += element.get();
        }
    }

    private final class NonDefaultIterator implements Iterator<Element> {
        private final DenseElement element = new DenseElement();
        private int index = -1;
        private int lookAheadIndex = -1;

        @Override
        public boolean hasNext() {
            if (lookAheadIndex == index) {  // User calls hasNext() after a next()
                lookAhead();
            } // else user called hasNext() repeatedly.
            return lookAheadIndex < size();
        }

        private void lookAhead() {
            lookAheadIndex++;
            while (lookAheadIndex < size() && values[lookAheadIndex] == 0.0) {
                lookAheadIndex++;
            }
        }

        @Override
        public Element next() {
            if (lookAheadIndex == index) { // If user called next() without checking hasNext().
                lookAhead();
            }

            Preconditions.checkState(lookAheadIndex > index);
            index = lookAheadIndex;

            if (index >= size()) { // If the end is reached.
                throw new NoSuchElementException();
            }

            element.index = index;
            return element;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    private final class AllIterator implements Iterator<Element> {
        private final DenseElement element = new DenseElement();

        private AllIterator() {
            element.index = -1;
        }

        @Override
        public boolean hasNext() {
            return element.index + 1 < size();
        }

        @Override
        public Element next() {
            if (element.index + 1 >= size()) { // If the end is reached.
                throw new NoSuchElementException();
            }
            element.index++;
            return element;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    private final class DenseElement implements Element {
        int index;

        @Override
        public double get() {
            return values[index];
        }

        @Override
        public int index() {
            return index;
        }

        @Override
        public void set(double value) {
            invalidateCachedLength();
            values[index] = value;
        }
    }
}
